﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using Newtonsoft.Json.Linq;

namespace VkApi
{
    public class Vkontakte
    {
        public string AppId { get; private set; }

        public VkAuthResult AuthResult { get; set; }

        public VkError LastError { get; set; }

        public event EventHandler<VkRequestEventArgs> RequestCompleted;

        public Vkontakte(string appId)
        {
            AppId = appId;
        }

        public string GetAuthUrl(string[] scopeSettings)
        {
            var parameters = new Dictionary<string, string>();
            parameters.Add("client_id", AppId);
            parameters.Add("redirect_uri", "http://api.vkontakte.ru/blank.html");
            parameters.Add("response_type", "token");
            parameters.Add("display", "popup");
            parameters.Add("scope", string.Join(",", scopeSettings));

            string url = VkConst.AuthorizationUrlBase + VkUtils.ToUrlParamString(parameters);
            return url;
        }

        private VkRequestEventArgs Invoke(string method, IEnumerable<KeyValuePair<string, string>> parameters)
        {
            var sb = new StringBuilder();
            sb.Append(VkConst.MethodUrlBase);
            sb.Append(method);
            sb.Append("?access_token=");
            sb.Append(AuthResult.AccessToken.Token);

            if (parameters != null)
            {
                foreach (var parameter in parameters)
                {
                    sb.Append("&");
                    sb.Append(parameter.Key);
                    sb.Append('=');
                    sb.Append(parameter.Value);
                }
            }

            return Request(method, sb.ToString());
        }

        private VkRequestEventArgs Request(string method, string query)
        {
            var webClient = new WebClient();
            webClient.Encoding = Encoding.UTF8;
            string result = webClient.DownloadString(new Uri(query));
            webClient.Dispose();
            var args = new VkRequestEventArgs(method, result);
            return args;
        }

        #region API Methods

        public List<VkNewsEntry> GetNews()
        {
            return GetNews(null, null, null, null, VkConst.MaxNews);
        }

        public List<VkNewsEntry> GetNews(string filters)
        {
            return GetNews(null, filters, null, null, VkConst.MaxNews);
        }

        public List<VkNewsEntry> GetNews(int count)
        {
            return GetNews(null, null, null, null, count);
        }

        public List<VkNewsEntry> GetNews(string sourceIds, string filters, string startTime, string endTime, int count)
        {
            var parameters = new Dictionary<string, string>();

            if (!string.IsNullOrEmpty(sourceIds))
                parameters.Add("source_ids", sourceIds);
            if (!string.IsNullOrEmpty(filters))
                parameters.Add("filters", filters);
            if (!string.IsNullOrEmpty(startTime))
                parameters.Add("start_time", startTime);
            if (!string.IsNullOrEmpty(endTime))
                parameters.Add("end_time", endTime);
            parameters.Add("count", count.ToString());

            var e = Invoke("newsfeed.get", parameters);
            if (e.Error != null)
                return null;

            var o = (JObject)e.Result;
            if (o["error"] != null)
            {
                LastError = new VkError();
                LastError.ErrorCode = int.Parse(o["error"]["error_code"].ToString());
                LastError.ErrorMessage = o["error"]["error_msg"].ToString();
                return null;
            }

            var result = new List<VkNewsEntry>();

            var profiles = new List<VkProfile>();
            var groups = new List<VkGroup>();

            //fill profiles
            foreach (var child in o["response"]["profiles"].Children())
            {
                var profile = new VkProfile();
                profile.Uid = long.Parse(child["uid"].ToString());
                profile.FirstName = child["first_name"].ToString();
                profile.LastName = child["last_name"].ToString();
                profile.PhotoUrl = child["photo"].ToString();
                profile.PhotoMediumUrl = profile.PhotoUrl = child["photo_medium_rec"].ToString();
                profile.Sex = VkProfile.ParseSex(child["sex"].ToString());
                profile.IsOnline = VkProfile.ParseOnline(child["online"].ToString());
                if (child["screen_name"] != null)
                    profile.ScreenName = child["screen_name"].ToString();
                profiles.Add(profile);
            }

            //fill groups
            foreach (var child in o["response"]["groups"].Children())
            {
                var group = new VkGroup();
                group.Gid = long.Parse(child["gid"].ToString());
                group.Name = child["name"].ToString();
                group.ScreenName = child["screen_name"].ToString();
                group.Type = VkGroup.ParseType(child["type"].ToString());
                group.PhotoUrl = child["photo"].ToString();
                group.PhotoMediumUrl = child["photo_medium"].ToString();
                group.PhotoBigUrl = child["photo_big"].ToString();
                groups.Add(group);
            }

            foreach (var child in o["response"]["items"].Children())
            {
                var newsEntry = new VkNewsEntry();
                newsEntry.Type = VkNewsEntry.ParseType(child["type"].ToString());
                newsEntry.SourceId = long.Parse(child["source_id"].ToString());
                newsEntry.Date = DateTimeConverter.FromUnixTime(child["date"].ToString()).ToLocalTime();
                if (child["text"] != null)
                    newsEntry.Text = child["text"].ToString();

                if (!newsEntry.IsGroupEntry)
                {
                    foreach (var profile in profiles)
                    {
                        if (profile.Uid == newsEntry.SourceId)
                        {
                            newsEntry.Sender = profile;
                            break;
                        }
                    }
                }
                else
                {
                    foreach (var group in groups)
                    {
                        if (group.Gid == -newsEntry.SourceId)
                        {
                            newsEntry.Sender = group;
                            break;
                        }
                    }
                }

                if (child["attachments"] != null)
                {
                    newsEntry.Attachments = new List<VkAttachment>();
                    foreach (var a in child["attachments"])
                    {
                        var attachment = new VkAttachment();
                        attachment.Type = VkAttachment.ParseType(a["type"].ToString());
                        switch (attachment.Type)
                        {
                            case VkAttachmentType.Audio:
                                var audio = new VkAudioAttachment();
                                audio.Id = long.Parse(a["audio"]["aid"].ToString());
                                audio.OwnerId = long.Parse(a["audio"]["owner_id"].ToString());
                                audio.Performer = a["audio"]["performer"].ToString();
                                audio.Title = a["audio"]["title"].ToString();
                                audio.Duration = int.Parse(a["audio"]["duration"].ToString());
                                attachment.AttachmentObject = audio;
                                break;

                            case VkAttachmentType.Link:
                                var link = new VkLinkAttachment();
                                link.Url = a["link"]["url"].ToString();
                                link.Title = a["link"]["title"].ToString();
                                link.Description = a["link"]["description"].ToString();
                                if (a["link"]["image_src"] != null)
                                    link.PreviewUrl = a["link"]["image_src"].ToString();

                                break;
                        }

                        newsEntry.Attachments.Add(attachment);
                    }
                }

                result.Add(newsEntry);
            }

            profiles.Clear();
            return result;
        }

        public List<VkAudio> SearchAudio(string query)
        {
            var result = SearchAudio(query, true, VkAudioSortType.Date, false, VkConst.MaxAudio, 0);
            return result;
        }

        public List<VkAudio> SearchAudio(string query, VkAudioSortType sortType)
        {
            var result = SearchAudio(query, true, sortType, false, VkConst.MaxAudio, 0);
            return result;
        }

        public List<VkAudio> SearchAudio(string query, int count, string forceArtist = null)
        {
            var result = SearchAudio(query, true, VkAudioSortType.Date, false, count, 0, forceArtist);
            return result;
        }

        public List<VkAudio> SearchAudio(string query, int count, int offset, string forceArtist = null)
        {
            var result = SearchAudio(query, true, VkAudioSortType.Date, false, count, offset, forceArtist);
            return result;
        }

        public List<VkAudio> SearchAudio(string query, bool autoCorrect, VkAudioSortType sortType, bool lyricsOnly, int count, int offset, string forceArtist = null)
        {
            var parameters = new Dictionary<string, string>();

            if (!string.IsNullOrEmpty(query))
                parameters.Add("q", query);
            if (autoCorrect)
                parameters.Add("auto_complete", "1");
            switch (sortType)
            {
                case VkAudioSortType.Date:
                    parameters.Add("sort", "0");
                    break;
                case VkAudioSortType.Duration:
                    parameters.Add("sort", "1");
                    break;
                case VkAudioSortType.Popularity:
                    parameters.Add("sort", "2");
                    break;
            }

            if (lyricsOnly)
                parameters.Add("lyrics", "1");
            if (count > 0)
                parameters.Add("count", count.ToString());
            if (offset > 0)
                parameters.Add("offset", offset.ToString());

            var e = Invoke("audio.search", parameters);

            if (e.Error != null)
                return null;

            var o = (JObject)e.Result;

            if (o["error"] != null)
            {
                LastError = new VkError();
                LastError.ErrorCode = int.Parse(o["error"]["error_code"].ToString());
                LastError.ErrorMessage = o["error"]["error_msg"].ToString();
                return null;
            }

            var result = new List<VkAudio>();

            foreach (var child in o["response"].Children())
            {
                if (!child.HasValues)
                    continue;
                var audio = new VkAudio();
                audio.Id = long.Parse(child["aid"].ToString());
                audio.OwnerId = long.Parse(child["owner_id"].ToString());
                audio.Artist = WebUtility.HtmlDecode(child["artist"].ToString()).Trim();
                if (!string.IsNullOrEmpty(forceArtist) && audio.Artist.ToLower() != forceArtist.ToLower())
                    continue;
                audio.Title = WebUtility.HtmlDecode(child["title"].ToString()).Trim();
                audio.Duration = TimeSpan.FromSeconds(int.Parse(child["duration"].ToString()));
                audio.Url = child["url"].ToString();
                result.Add(audio);
            }

            return result;
        }

        public List<VkAudio> GetAudio()
        {
            return GetAudio(null, null, null, 0, 0);
        }

        public List<VkAudio> GetAudio(int count)
        {
            return GetAudio(null, null, null, count, 0);
        }

        public List<VkAudio> GetAudio(int count, int offset)
        {
            return GetAudio(null, null, null, count, offset);
        }

        public List<VkAudio> GetAudio(string userId)
        {
            return GetAudio(userId, null, null, 0, 0);
        }

        public List<VkAudio> GetAudio(string userId, int count, int offset)
        {
            return GetAudio(userId, null, null, count, offset);
        }

        public List<VkAudio> GetAudio(string userId, string groupId, string albumId, int count, int offset)
        {
            var parameters = new Dictionary<string, string>();

            if (!string.IsNullOrEmpty(userId))
                parameters.Add("uid", userId);

            if (!string.IsNullOrEmpty(groupId))
                parameters.Add("gid", groupId);

            if (!string.IsNullOrEmpty(albumId))
                parameters.Add("album_id", albumId);

            if (offset != 0)
                parameters.Add("offset", offset.ToString());

            if (count != 0)
                parameters.Add("count", count.ToString());

            var e = Invoke("audio.get", parameters);

            if (e.Error != null)
                return null;

            var o = (JObject)e.Result;

            if (o["error"] != null)
            {
                LastError = new VkError();
                LastError.ErrorCode = int.Parse(o["error"]["error_code"].ToString());
                LastError.ErrorMessage = o["error"]["error_msg"].ToString();
                return null;
            }

            var result = new List<VkAudio>();

            foreach (var child in o["response"].Children())
            {
                var audio = new VkAudio();
                audio.Id = long.Parse(child["aid"].ToString());
                audio.OwnerId = long.Parse(child["owner_id"].ToString());
                audio.Artist = WebUtility.HtmlDecode(child["artist"].ToString()).Trim();
                audio.Title = WebUtility.HtmlDecode(child["title"].ToString()).Trim();
                audio.Duration = TimeSpan.FromSeconds(int.Parse(child["duration"].ToString()));
                audio.Url = child["url"].ToString();
                if (userId == null || userId == AuthResult.UserId)
                    audio.IsAddedByUser = true;
                result.Add(audio);
            }

            return result;
        }

        public void AddAudio(string aid, string oid)
        {
            var parameters = new Dictionary<string, string>();

            parameters.Add("aid", aid);
            parameters.Add("oid", oid);

            Invoke("audio.add", parameters);
        }

        public void DeleteAudio(string aid, string oid)
        {
            var parameters = new Dictionary<string, string>();

            parameters.Add("aid", aid);
            parameters.Add("oid", oid);

            Invoke("audio.delete", parameters);
        }

        public VkProfile GetUserProfile()
        {
            var result = GetProfiles(AuthResult.UserId, "photo", null);
            if (result != null && result.Count > 0)
                return result[0];
            return null;
        }

        public List<VkProfile> GetProfiles(string userIds, string fields, string nameCase)
        {
            var parameters = new Dictionary<string, string>();

            parameters.Add("uids", userIds);

            if (!string.IsNullOrEmpty(fields))
                parameters.Add("fields", fields);

            if (!string.IsNullOrEmpty(nameCase))
                parameters.Add("name_case", nameCase);

            var e = Invoke("getProfiles", parameters);

            if (e.Error != null)
                return null;

            var o = (JObject)e.Result;

            if (o["error"] != null)
            {
                LastError = new VkError();
                LastError.ErrorCode = int.Parse(o["error"]["error_code"].ToString());
                LastError.ErrorMessage = o["error"]["error_msg"].ToString();
                return null;
            }

            var result = new List<VkProfile>();

            //fill profiles
            foreach (var child in o["response"].Children())
            {
                var profile = new VkProfile();
                profile.Uid = long.Parse(child["uid"].ToString());
                profile.FirstName = child["first_name"].ToString();
                profile.LastName = child["last_name"].ToString();
                if (child["photo"] != null)
                    profile.PhotoUrl = child["photo"].ToString();
                if (child["photo_medium_rec"] != null)
                    profile.PhotoMediumUrl = profile.PhotoUrl = child["photo_medium_rec"].ToString();
                if (child["sex"] != null)
                    profile.Sex = VkProfile.ParseSex(child["sex"].ToString());
                if (child["online"] != null)
                    profile.IsOnline = VkProfile.ParseOnline(child["online"].ToString());
                if (child["screen_name"] != null)
                    profile.ScreenName = child["screen_name"].ToString();
                result.Add(profile);
            }

            return result;
        }

        public List<VkMessage> GetHistory(string uid, int count, int offset)
        {
            var parameters = new Dictionary<string, string>();

            parameters.Add("uid", uid);

            if (offset > 0)
                parameters.Add("offset", offset.ToString());

            if (count > 0)
                parameters.Add("count", count.ToString());

            var e = Invoke("messages.getHistory", parameters);

            if (e.Error != null)
                return null;

            var o = (JObject)e.Result;

            if (o["error"] != null)
            {
                LastError = new VkError();
                LastError.ErrorCode = int.Parse(o["error"]["error_code"].ToString());
                LastError.ErrorMessage = o["error"]["error_msg"].ToString();
                return null;
            }

            var result = new List<VkMessage>();

            //fill profiles
            foreach (var child in o["response"].Children())
            {
                if (!child.HasValues)
                    continue;
                var message = new VkMessage();
                message.Body = child["body"].ToString();
                message.Mid = child["mid"].ToString();
                message.FromId = child["from_id"].ToString();
                message.Date = DateTimeConverter.FromUnixTime(child["date"].ToString());
                message.IsRead = (int)child["read_state"] == 1;
                result.Add(message);
            }

            return result;
        }

        public int GetUnreadMessagesCount()
        {
            var parameters = new Dictionary<string, string>();

            parameters.Add("count", "1");
            parameters.Add("filters", ((int)VkMessageFilter.Unread).ToString());

            var e = Invoke("messages.get", parameters);

            if (e.Error != null)
                return 0;

            var o = (JObject)e.Result;

            if (o["error"] != null)
            {
                LastError = new VkError();
                LastError.ErrorCode = int.Parse(o["error"]["error_code"].ToString());
                LastError.ErrorMessage = o["error"]["error_msg"].ToString();
                return 0;
            }

            int unread = 0;
            if (o["response"].HasValues)
                unread = (int)o["response"][0];

            return unread;
        }

        //public List<VkMessage> GetMessages(bool outMessages, int count, int offset, VkMessageFilter filter, int previewLength)
        //{
        //    var parameters = new Dictionary<string, string>();

        //    parameters.Add("out", outMessages ? "1" : "0");

        //    if (offset > 0)
        //        parameters.Add("offset", offset.ToString());

        //    if (count > 0)
        //        parameters.Add("count", count.ToString());

        //    if (filter != VkMessageFilter.None)
        //        parameters.Add("filters", ((int)filter).ToString());

        //    parameters.Add("preview_length", previewLength.ToString());

        //    var e = Invoke("messages.get", parameters);

        //    if (e.Error != null)
        //        return null;

        //    var o = (JObject)e.Result;

        //    if (o["error"] != null)
        //    {
        //        LastError = new VkError();
        //        LastError.ErrorCode = int.Parse(o["error"]["error_code"].ToString());
        //        LastError.ErrorMessage = o["error"]["error_msg"].ToString();
        //        return null;
        //    }

        //    var result = new List<VkMessage>();

        //    //fill profiles
        //    foreach (var child in o["response"].Children())
        //    {
        //        if (!child.HasValues)
        //            continue;
        //        var message = new VkMessage();
        //        message.Body = child["body"].ToString();
        //        message.Mid = child["mid"].ToString();
        //        message.FromId = child["from_id"].ToString();
        //        message.Date = DateTimeConverter.FromUnixTime(child["date"].ToString());
        //        message.IsRead = (int)child["read_state"] == 1;
        //        result.Add(message);
        //    }

        //    return result;
        //}

        #endregion
    }
}
